/* eslint-disable @typescript-eslint/no-explicit-any */
import IndexedDBService, { IDataStructure } from '../../utils/indexedDB';
import { saveToLocalStore, dataFromLocalStore, removeFromLocalStore } from '../../utils/store';
import { fetchUserData } from '../../utils/user';
import { post, customError, buildResult } from '../index';
import { ReturnType, RankingResponse, RankingInfo } from '../interface'
import { URL_DETECTOR_RANKING, URL_INVITE_DAILY_RANKING, URL_INVITE_RANKING } from '../base/url'
import { HOUR_MILL } from '../../constants';

export enum RankType {
    Invite = 'invite',
    Detector = 'detector',
    InviteDaily = 'inviteDaily'
}

interface RankData extends RankingInfo, IDataStructure {
    snapTime?: string;
    isSelf?: true
}

class RankDataManager {
    private dbServiceCache: { [key: string]: IndexedDBService<RankData> }
    private updateTimeCache: { [key: string]: number }
    private updateTimeKey: string = 'UPDATE_TIME_KEY'
    private updateInterval: number = 1 * HOUR_MILL// default 1 hour
    private customUpdateInterval: { [key in RankType]?: number } = { [RankType.InviteDaily]: 0.5 * HOUR_MILL } // set update interval, if null use default 'updateInterval'
    constructor() {
        this.dbServiceCache = {}
        this.updateTimeCache = {}
    }

    private static manager: RankDataManager
    static shareInstance(): RankDataManager {
        if (!this.manager) {
            this.manager = new RankDataManager()
        }
        return this.manager
    }

    async init(name: RankType) {
        if (!this.dbServiceCache[name]) {
            this.dbServiceCache[name] = new IndexedDBService<RankData>('EarthDatabase', name);
            await this.dbServiceCache[name].init();
        }
    }

    updateTime(name: RankType) {
        const time = new Date().getTime()
        this.updateTimeCache[name] = time
        try {
            saveToLocalStore(this.updateTimeKey, JSON.stringify(this.updateTimeCache))
        } catch (error) {
            // temp
        }
    }

    removeUpdateTime() {
        try {
            removeFromLocalStore(this.updateTimeKey)
        } catch (error) {
            // temp
        }
    }

    getLastUpdateTime(name: RankType) {
        let time = this.updateTimeCache[name];
        if (!time) {
            try {
                const str = dataFromLocalStore(this.updateTimeKey)
                if (!str) {
                    return 0
                }

                const json = JSON.parse(str)
                this.updateTimeCache = {
                    ...json,
                    ...this.updateTimeCache
                }
                time = this.updateTimeCache[name];
            } catch (error) {
                return 0
            }
        }
        return time || 0
    }

    async fetchDataIfNeed(name: RankType): Promise<RankData[]> {
        const time = this.getLastUpdateTime(name);
        const now = new Date().getTime()

        const interval = this.customUpdateInterval[name] || this.updateInterval
        const needFetch = now - time > interval;
        if (!needFetch) {
            const list = await this.getData(name)
            if (list?.length) {
                return list
            } else {
                this.removeUpdateTime()
                delete this.updateTimeCache[name]
            }
        }

        const res = await fetchRankingData({ type: name })
        if (res.success) {
            this.updateTime(name)
            const user = fetchUserData()
            const self: RankData = {
                serNo: res.data?.self?.serNo || 0,
                num: res.data?.self?.num || 0,
                image: res.data?.self?.image || user?.icon || '',
                nickName: res.data?.self?.nickName || user?.username || '',
                snapTime: res.data?.snapTime || '',
                isSelf: true
            }
            const list = [self, ...(res.data?.items || [])]
            this.clear(name)
            this.saveData(list, name)
            return list
        }
        return []
    }

    async saveData(data: RankData[], name: RankType) {
        try {
            const dbService = this.dbServiceCache[name]
            if (!dbService) {
                return
            }
            await dbService.bulkAdd(data)
        } catch (error) {
            // temp
        }
    }

    async getData(name: RankType): Promise<RankData[]> {
        try {
            const dbService = this.dbServiceCache[name]
            if (!dbService) {
                return []
            }
            return dbService.getAll()
        } catch (error) {
            return []
        }
    }


    async clear(name: RankType) {
        try {
            const dbService = this.dbServiceCache[name]
            if (!dbService) {
                return
            }
            await dbService.clear()
        } catch (error) {
            // temp
        }
    }

}

export type RankInfo = {
    items: RankData[],
    snapTime: string
}

export const ranking = async (params: { type: RankType }): Promise<ReturnType<RankInfo | undefined>> => {
    const manager = RankDataManager.shareInstance()
    await manager.init(params.type)
    const list = await manager.fetchDataIfNeed(params.type)
    const found = list.find(e => e.isSelf)
    return buildResult({
        items: list,
        snapTime: found?.snapTime || ''
    }, 0, '')
}

const fetchRankingData = async (params: { type: RankType }): Promise<ReturnType<RankingResponse | undefined>> => {
    try {
        const map = {
            [RankType.InviteDaily]: URL_INVITE_DAILY_RANKING,
            [RankType.Invite]: URL_INVITE_RANKING,
            [RankType.Detector]: URL_DETECTOR_RANKING,
        }
        const result = await post<RankingResponse>(map[params.type])
        return result
    } catch (error: any) {
        return customError(error)
    }
}
